SPDX-FileCopyrightText: 2025 Alyssar Dunia & Loïs Anseel SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
import bpy
import math
import bmesh
import mathutils
import random
import pprintCLEAN UP SCENE
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.ops.outliner.orphans_purge()OPTIMIZED GEOMETRY CREATION (one mesh subdivided rather than accumulation of individual elements)
def generate_object_from_grid(grid_pattern, layers, obj_name, position=(0, 0, 0)):Dimensions of each cube
    cube_size = 0.5Create a new mesh and object
    mesh = bpy.data.meshes.new(obj_name)
    obj = bpy.data.objects.new(obj_name, mesh)
    bpy.context.collection.objects.link(obj)Generate vertices and faces
    vertices = []
    faces = []
    vertex_index = 0
    for z in range(layers):
        for y, row in enumerate(grid_pattern):
            for x, cell in enumerate(row):
                if cell == 1:  # If the cell is 1, add a cubeDefine 8 vertices of the cube
                    cube_vertices = [
                        (x * cube_size, y * cube_size, z * cube_size),
                        (x * cube_size + cube_size, y * cube_size, z * cube_size),
                        (
                            x * cube_size + cube_size,
                            y * cube_size + cube_size,
                            z * cube_size,
                        ),
                        (x * cube_size, y * cube_size + cube_size, z * cube_size),
                        (x * cube_size, y * cube_size, z * cube_size + cube_size),
                        (
                            x * cube_size + cube_size,
                            y * cube_size,
                            z * cube_size + cube_size,
                        ),
                        (
                            x * cube_size + cube_size,
                            y * cube_size + cube_size,
                            z * cube_size + cube_size,
                        ),
                        (
                            x * cube_size,
                            y * cube_size + cube_size,
                            z * cube_size + cube_size,
                        ),
                    ]Define 6 faces of the cube
                    cube_faces = [
                        (
                            vertex_index,
                            vertex_index + 1,
                            vertex_index + 2,
                            vertex_index + 3,
                        ),
                        (
                            vertex_index + 4,
                            vertex_index + 5,
                            vertex_index + 6,
                            vertex_index + 7,
                        ),
                        (
                            vertex_index,
                            vertex_index + 1,
                            vertex_index + 5,
                            vertex_index + 4,
                        ),
                        (
                            vertex_index + 1,
                            vertex_index + 2,
                            vertex_index + 6,
                            vertex_index + 5,
                        ),
                        (
                            vertex_index + 2,
                            vertex_index + 3,
                            vertex_index + 7,
                            vertex_index + 6,
                        ),
                        (
                            vertex_index + 3,
                            vertex_index + 0,
                            vertex_index + 4,
                            vertex_index + 7,
                        ),
                    ]Add vertices and faces to the lists
                    vertices.extend(cube_vertices)
                    faces.extend(cube_faces)Update vertex index for the next cube
                    vertex_index += 8Create the mesh from vertices and faces
    mesh.from_pydata(vertices, [], faces)
    mesh.update()Set object position
    obj.location = positiondef create_rotated_structure(
    grid_pattern,
    num_stacks,
    obj_name,
    position=(0, 0, 0),
    initial_rotation=2,
    rotation_increment=2,
    increment_factor=0.5,
    scale_ones=1.0,
):Grid and object parameters
    grid_size = len(grid_pattern[0])
    cell_length = 1
    cell_height = 0.75
    cell_depth = 1Create a new mesh and object
    mesh = bpy.data.meshes.new(obj_name)
    obj = bpy.data.objects.new(obj_name, mesh)
    bpy.context.collection.objects.link(obj)Initialize bmesh for efficient geometry creation
    bm = bmesh.new()    def create_layer(base_z, rotation_angle):
        cos_theta = math.cos(rotation_angle)
        sin_theta = math.sin(rotation_angle)
        for row, pattern_row in enumerate(grid_pattern):
            for col, cell in enumerate(pattern_row):
                if cell == 1:
                    x = (
                        col * cell_length
                        - (grid_size * cell_length / 2)
                        + (cell_length / 2)
                    )
                    y = (
                        row * cell_depth
                        - (grid_size * cell_depth / 2)
                        + (cell_depth / 2)
                    )
                    rotated_x = cos_theta * x - sin_theta * y
                    rotated_y = sin_theta * x + cos_theta * yCreate a cube and apply scaling for “1”
                    bmesh.ops.create_cube(
                        bm,
                        size=1.0,
                        matrix=mathutils.Matrix.Translation(
                            (rotated_x, rotated_y, base_z)
                        ),
                    )
                    bmesh.ops.scale(
                        bm,
                        verts=bm.verts[-8:],
                        vec=(
                            cell_length / 2 * scale_ones,
                            cell_depth / 2 * scale_ones,
                            cell_height / 2 * scale_ones,
                        ),
                    )Create layers
    current_increment = rotation_increment
    for stack in range(num_stacks):
        layer_z = stack * cell_height
        angle = math.radians(initial_rotation + current_increment * stack)
        create_layer(layer_z, angle)
        current_increment += increment_factorFinalize mesh
    bm.to_mesh(mesh)
    bm.free()Set object position
    obj.location = positionDEFINE BINARY PATTERN
pattern = [
    random.choice([1, 0]) for _ in range(18)
]  # [1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1]
grid_pattern = []
pattern.reverse()
grid_pattern.append(pattern.copy())
pattern.reverse()
for i in range(len(pattern) - 2):
    line = []
    line.append(pattern[i + 1])
    line.extend([0 for _ in range(len(pattern) - 2)])
    line.append(pattern[-i - 1])
    grid_pattern.append(line.copy())
grid_pattern.append(pattern.copy())
print("Pattern:")
pprint.pprint(grid_pattern)PARAMETER AND EXECUTE OBJECT CREATION Number of layers to stack on Z-axis for the grid object
grid_layers = 100Create grid object
generate_object_from_grid(
    grid_pattern, grid_layers, "GridObject", position=(-4.25, -4.25, -0.5)
)Parameters for rotated structure
num_stacks = 180
initial_rotation = 2
rotation_increment = 2
increment_factor = 1
scale_ones = 1Create rotated structure
create_rotated_structure(
    grid_pattern,
    num_stacks,
    "RotatedStructure",
    position=(0, 0, 0),
    initial_rotation=initial_rotation,
    rotation_increment=rotation_increment,
    increment_factor=increment_factor,
    scale_ones=scale_ones,
)ISOLATE RANDOM PORTION OF TOWER Create a cube for intersection
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), scale=(7, 7, 5))Translate the cube randomly along the Z-axis
bpy.ops.transform.translate(value=(0, 0, random.uniform(0, 50)))Select both objects and the cube
grid_object = bpy.data.objects["GridObject"]
rotated_structure = bpy.data.objects["RotatedStructure"]
intersect_cube = bpy.data.objects["Cube"]Ensure both objects are selected
grid_object.select_set(True)
rotated_structure.select_set(True)Add a boolean modifier to both objects
bpy.context.view_layer.objects.active = (
    grid_object  # Set an object as active for context
)
for obj in [grid_object, rotated_structure]:
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.modifier_add(type="BOOLEAN")
    obj.modifiers["Boolean"].operation = "INTERSECT"
    obj.modifiers["Boolean"].solver = "FAST"
    obj.modifiers["Boolean"].object = intersect_cubeApply modifiers to all selected objects at once
bpy.ops.object.select_all(action="DESELECT")
grid_object.select_set(True)
rotated_structure.select_set(True)
bpy.ops.object.convert(target="MESH")  # Applies all modifiers for selected objectsDelete the cube
bpy.ops.object.select_all(action="DESELECT")
intersect_cube.select_set(True)
bpy.ops.object.delete(use_global=False)